package fimuni.pb138.repositar;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.basex.core.cmd.Open;
import org.basex.server.ClientQuery;
import org.basex.server.ClientSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class XSDService {

	private ClientSession session;
	private static final Logger logger = LoggerFactory
			.getLogger(XSDService.class);

	/**
	 * Constructor for XSDService
	 * Establishes connection to BaseX database
	 */
	public XSDService() {
		try {
			session = new ClientSession(ArtifactUtils.DATABASE_SERVER,
					ArtifactUtils.DATABASE_PORT,
					ArtifactUtils.DATABASE_USERNAME,
					ArtifactUtils.DATABASE_PASSWORD);
			session.execute(new Open(ArtifactUtils.DATABASE_NAME));
		} catch (IOException e) {
			logger.error(e.getLocalizedMessage());
		}
	}

	/**
	 * Inserts XSD file into database. Creates new version of file If it already exist in DB, 
	 * or creates new record for the file
	 * 
	 * @param file file to be inserted into DB
	 * @return
	 */
	public String insertXSD(XSDFile file) {
		try {
			ClientQuery cq = session.query("count(/xsds/xsd[name=\""
					+ file.getName() + "\"])");
			boolean exists = !cq.execute().equals("0");
			if (exists) {
				StringBuilder query = new StringBuilder();
				query.append("insert node");
				cq = session.query("max(/xsds/xsd[name=\"" + file.getName()
						+ "\"]/versions/version/@number)");
				int lastVersionNumber = Integer.valueOf(cq.execute()) + 1;
				query.append("<version number=\"" + lastVersionNumber
						+ "\" fileName=\"" + (file.getFileName()) + "\">");
				query.append("<simpleTypes>");
				for (String simpleType : file.getSimpleTypes()) {
					query.append("<simpleType>" + simpleType + "</simpleType>");
				}
				query.append("</simpleTypes>");
				query.append("<complexTypes>");
				for (String complexType : file.getComplexTypes()) {
					query.append("<complexType>" + complexType
							+ "</complexType>");
				}
				query.append("</complexTypes>");
				query.append("<elements>");
				for (String element : file.getElements()) {
					query.append("<element>" + element + "</element>");
				}
				query.append("</elements>");
				query.append("<attributes>");
				for (String attribute : file.getAttributes()) {
					query.append("<attribute>" + attribute + "</attribute>");
				}
				query.append("</attributes>");
				query.append("</version>");
				query.append("into /xsds/xsd[name=\"" + file.getName()
						+ "\"]/versions");
				cq = session.query(query.toString());
			} else {
				StringBuilder query = new StringBuilder();
				query.append("insert node");
				cq = session.query("max(/xsds/xsd/@id)");
				int lastId = Integer.valueOf(cq.execute()) + 1;
				query.append("<xsd id=\"" + lastId + "\">");
				query.append("<name>" + file.getName() + "</name>");
				query.append("<versions>");
				query.append("<version number=\"1\" fileName=\""
						+ (file.getFileName()) + "\">");
				query.append("<simpleTypes>");
				for (String simpleType : file.getSimpleTypes()) {
					query.append("<simpleType>" + simpleType + "</simpleType>");
				}
				query.append("</simpleTypes>");
				query.append("<complexTypes>");
				for (String complexType : file.getComplexTypes()) {
					query.append("<complexType>" + complexType
							+ "</complexType>");
				}
				query.append("</complexTypes>");
				query.append("<elements>");
				for (String element : file.getElements()) {
					query.append("<element>" + element + "</element>");
				}
				query.append("</elements>");
				query.append("<attributes>");
				for (String attribute : file.getAttributes()) {
					query.append("<attribute>" + attribute + "</attribute>");
				}
				query.append("</attributes>");
				query.append("</version>");
				query.append("</versions>");
				query.append("</xsd>");
				query.append("into /xsds");
				cq = session.query(query.toString());
			}
			cq.execute();
		} catch (IOException e) {
			logger.error(e.getLocalizedMessage());
		}
		return "";
	}

	/**
	 * Finds artifact by id in database
	 * 
	 * @param id id of wanted artifact
	 * @return list of all versions of specified artifact
	 */
	public List<XSDFile> findXSDbyID(long id) {
		List<XSDFile> xsdFiles = new ArrayList<>();
		try {
			ClientQuery cq = session.query("count(/xsds/xsd[@id=\"" + id
					+ "\"]/versions/version)");
			int versionCount = Integer.parseInt(cq.execute());
			cq = session.query("/xsds/xsd[@id=\"" + id + "\"]/name/text()");
			String name = cq.execute();
			for (int i = 1; i <= versionCount; i++) {
				XSDFile xsdFile = new XSDFile();
				xsdFile.setId(id);
				xsdFile.setName(name);
				xsdFile.setVersion(i);
				
				cq = session.query("string(/xsds/xsd[@id=\"" + id
						+ "\"]/versions/version[" + i
						+ "]/@fileName)");
				xsdFile.setFileName(cq.execute());

				List<String> simpleTypes = new ArrayList<>();
				cq = session.query("/xsds/xsd[@id=\"" + id
						+ "\"]/versions/version[" + i
						+ "]/simpleTypes/simpleType/text()");
				while (cq.more()) {
					simpleTypes.add(cq.next());
				}
				xsdFile.setSimpleTypes(simpleTypes);

				List<String> complexTypes = new ArrayList<>();
				cq = session.query("/xsds/xsd[@id=\"" + id
						+ "\"]/versions/version[" + i
						+ "]/complexTypes/complexType/text()");
				while (cq.more()) {
					complexTypes.add(cq.next());
				}
				xsdFile.setComplexTypes(complexTypes);

				List<String> elements = new ArrayList<>();
				cq = session.query("/xsds/xsd[@id=\"" + id
						+ "\"]/versions/version[" + i
						+ "]/elements/element/text()");
				while (cq.more()) {
					elements.add(cq.next());
				}
				xsdFile.setElements(elements);

				List<String> attributes = new ArrayList<>();
				cq = session.query("/xsds/xsd[@id=\"" + id
						+ "\"]/versions/version[" + i
						+ "]/attributes/attribute/text()");
				while (cq.more()) {
					attributes.add(cq.next());
				}
				xsdFile.setAttributes(attributes);

				xsdFiles.add(xsdFile);
			}

		} catch (IOException e) {
			logger.error(e.getLocalizedMessage());
		}
		return xsdFiles;
	}
	
	/**
	 * Finds artifact by id in database
	 * 
	 * @param id id of wanted artifact
	 * @param version version of wanted artifact
	 * @return version of specified artifact
	 */
	public XSDFile findXSDbyIDandVersion(long id, long version) {
		List<XSDFile> xsdFiles = findXSDbyID(id);
		for(XSDFile file : xsdFiles) {
			if(file.getVersion() == version) {
				return file;
			}
		}
		
		return null;
	}
	
	/**
	 * Stores uploaded file to database
	 * 
	 * @param file uploaded file
	 */
	public void processXSD(MultipartFile file) {
		String fileName = ArtifactUtils.saveFile(file);
		XSDFile xsdFile = new XSDFile();
		String oldName = file.getOriginalFilename();
		xsdFile.setName(oldName);
		xsdFile.setFileName(fileName);

		String attributeNames = XQueryProcessor
				.processQuery(ArtifactUtils.XQUERY_XSD_ATTRIBUTES.replace(
						"DOCNAME",
						ArtifactUtils.STORAGE_PATH.replace("\\", "/") + xsdFile.getFileName()));
		List<String> attributes;
		if (attributeNames.isEmpty()) {
			attributes = new ArrayList<>();
		} else {
			attributes = new ArrayList<>(Arrays.asList(attributeNames.split("\\|")));
		}
		xsdFile.setAttributes(attributes);

		String elementNames = XQueryProcessor
				.processQuery(ArtifactUtils.XQUERY_XSD_ELEMENTS.replace(
						"DOCNAME",
						ArtifactUtils.STORAGE_PATH.replace("\\", "/") + xsdFile.getFileName()));
		List<String> elements;
		if (elementNames.isEmpty()) {
			elements = new ArrayList<>();
		} else {
			elements = new ArrayList<>(Arrays.asList(elementNames.split("\\|")));
		}
		xsdFile.setElements(elements);
		
		String simpleTypeNames = XQueryProcessor
				.processQuery(ArtifactUtils.XQUERY_XSD_SIMPLETYPES.replace(
						"DOCNAME",
						ArtifactUtils.STORAGE_PATH.replace("\\", "/") + xsdFile.getFileName()));
		List<String> simpleTypes;
		if (simpleTypeNames.isEmpty()) {
			simpleTypes = new ArrayList<>();
		} else {
			simpleTypes = new ArrayList<>(Arrays.asList(simpleTypeNames.split("\\|")));
		}
		xsdFile.setSimpleTypes(simpleTypes);
		
		String complexTypeNames = XQueryProcessor
				.processQuery(ArtifactUtils.XQUERY_XSD_COMPLEXTYPES.replace(
						"DOCNAME",
						ArtifactUtils.STORAGE_PATH.replace("\\", "/") + xsdFile.getFileName()));
		List<String> complexTypes;
		if (complexTypeNames.isEmpty()) {
			complexTypes = new ArrayList<>();
		} else {
			complexTypes = new ArrayList<>(Arrays.asList(complexTypeNames.split("\\|")));
		}
		xsdFile.setComplexTypes(complexTypes);

		insertXSD(xsdFile);
	}
	
	/**
	 * Finds artifact by its metadata in database 
	 * @param name name of the artifact
	 * @param simpleType simpleType name used in schema
	 * @param complexType complexType name used in schema
	 * @param attribute attribute name used in schema
	 * @param element element name used in schema
	 * @return list of all versions of specified artifact
	 */
	public List<XSDFile> findXSDbyMetadata(String name, String simpleType,
			String complexType, String attribute, String element) {
		if(name == null)
			name = "";
		if(simpleType == null)
			simpleType = "";
		if(complexType == null)
			complexType = "";
		if(attribute == null)
			attribute = "";
		if(element == null)
			element = "";
		
		List<XSDFile> xsdFiles = getAllXSDs();

		// filter all unwanted versions
		Iterator<XSDFile> i = xsdFiles.iterator();
		while (i.hasNext()) {
			XSDFile file = i.next();
			if( (!file.getName().equals(name) && !name.isEmpty())
					|| (!file.getSimpleTypes().contains(simpleType) && !simpleType.isEmpty())
					|| (!file.getComplexTypes().contains(complexType) && !complexType.isEmpty())
					|| (!file.getAttributes().contains(attribute) && !attribute.isEmpty())
					|| (!file.getElements().contains(element) && !element.isEmpty())) {
				i.remove();
				continue;
			}
		}
		
		return xsdFiles;
	}
	
	/**
	 * Get all XSD files from database
	 * 
	 * @return list of all XSD files
	 */
	public List<XSDFile> getAllXSDs() {
		List<XSDFile> XSDList = new ArrayList<>();
		try {
			ClientQuery cq = session.query("count(/xsds/xsd)");
			int xsdCount = Integer.parseInt(cq.execute());
			for(int i = 1; i <= xsdCount; i++) {
				List<XSDFile> versionList = findXSDbyID(i);
				XSDFile xsd = versionList.get(versionList.size() - 1);
				XSDList.add(xsd);
			}	
		}
		catch (Exception e) {
			logger.error(e.getLocalizedMessage());
		}		
		return XSDList;
	}
}
